// ==++==
// 
//   Copyright (c) 2008 Microsoft Corporation. All rights reserved.
//   License: MS-Pl
//
// ==--==

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Pex.Engine.ComponentModel;
using Microsoft.Pex.Engine.TestFrameworks;
using Microsoft.ExtendedReflection.Metadata;
using NUnit.Framework;
using Microsoft.ExtendedReflection.Utilities.Safe.Diagnostics;
using System.IO;
using Microsoft.ExtendedReflection.Metadata.Names;
using Microsoft.ExtendedReflection.Utilities.Safe;
using Microsoft.ExtendedReflection.Collections;
using Microsoft.Pex.Framework.TestFrameworks;

namespace Pex.NUnit
{
    /// <summary>
    /// NUnit test framework
    /// </summary>
    internal sealed class NUnitTestFrameworkAttribute :
        PexTestFrameworkAttributeBase
    {
        public NUnitTestFrameworkAttribute()
        {
            this.SetAsDefault = true;
        }

        protected override IPexTestFramework CreateTestFramework(IPexComponent host)
        {
            SafeDebug.AssumeNotNull(host, "host");
            return new TestFramework(host);
        }

        [Serializable]
        class TestFramework :
            AttributeBasedTestFrameworkBase
        {
            public TestFramework(IPexComponent host)
                : base(host)
            {
            }

            /// <summary>
            /// identify of the test framework
            /// </summary>
            /// <value></value>
            public override string Name
            {
                get { return "NUnit"; }
            }

            /// <summary>
            /// Gets the assembly name of the framework main's assembly. This name is used
            /// to automatically discover test frameworks, based the assembly references
            /// </summary>
            /// <value></value>
            public override ShortAssemblyName AssemblyName
            {
                get { return Metadata<TestFixtureAttribute>.Assembly.SerializableName; }
            }

            /// <summary>
            /// Gets the root namespace.
            /// </summary>
            /// <value>The root namespace.</value>
            public override string RootNamespace
            {
                get { return typeof(TestFixtureAttribute).Namespace; }
            }

            private string hintPath = null;
            /// <summary>
            /// Hint on the location of the test framework assembly
            /// </summary>
            /// <param name="assemblyFileName">Name of the assembly file.</param>
            /// <returns></returns>
            protected override string GetHintPath(string assemblyFileName)
            {
                SafeDebug.AssumeNotNullOrEmpty(assemblyFileName, "assemblyFileName");

                if (this.hintPath == null)
                {
                    DirectoryInfo programFiles = new DirectoryInfo(Environment.ExpandEnvironmentVariables("%ProgramFiles%"));
                    DirectoryInfo[] info = programFiles.GetDirectories("NUnit-Net-*", SearchOption.TopDirectoryOnly);
                    if (info == null || info.Length == 0)
                        this.hintPath = String.Empty;
                    else
                        this.hintPath = Path.Combine(info[0].FullName, "bin");
                }

                if (this.hintPath == String.Empty)
                    return null;
                else
                    return Path.Combine(this.hintPath, assemblyFileName);
            }

            /// <summary>
            /// Gets a value indicating whether
            /// partial test classes
            /// </summary>
            /// <value></value>
            public override bool SupportsPartialClasses
            {
                get { return true; }
            }

            /// <summary>
            /// Gets the ExpectedException attribute.
            /// </summary>
            /// <value>The expected exception attribute.</value>
            public override TypeName ExpectedExceptionAttribute
            {
                get { return Metadata<ExpectedExceptionAttribute>.SerializableName; }
            }

            /// <summary>
            /// Tries the read expected exception.
            /// </summary>
            /// <param name="target">The method.</param>
            /// <param name="exceptionType">Type of the exception.</param>
            /// <returns></returns>
            public override bool TryReadExpectedException(
                ICustomAttributeProviderEx target,
                out TypeEx exceptionType)
            {
                exceptionType = null;

                ExpectedExceptionAttribute attribute = AttributeHelper.GetAttribute(
                    target,
                    this.ExpectedExceptionAttribute)
                    as ExpectedExceptionAttribute;

                if (attribute != null)
                {
                    if (attribute.ExceptionType != null)
                        exceptionType = MetadataFromReflection.GetType(attribute.ExceptionType);
                    else
                    {
                        string exceptionName = attribute.ExceptionName;
                        if (!SafeString.IsNullOrEmpty(exceptionName))
                        {
                            Type t = Type.GetType(exceptionName, false);
                            if (t != null)
                                exceptionType = MetadataFromReflection.GetType(t);
                        }
                    }
                }

                return exceptionType != null;
            }

            /// <summary>
            /// Tries to get the assembly set up tear down attribute.
            /// </summary>
            /// <param name="assembly">The assembly.</param>
            /// <param name="setUp">The set up.</param>
            /// <param name="tearDown">The tear down.</param>
            /// <returns></returns>
            public override bool TryGetAssemblySetupTeardownMethods(
                AssemblyEx assembly,
                out Method setUp,
                out Method tearDown)
            {
                setUp = null;
                tearDown = null;
                return false;
            }

            /// <summary>
            /// Gets a value indicating whether[fixture set up tear down are instance methods.
            /// </summary>
            /// <value>
            /// 	<c>true</c> if [fixture set up tear down instance]; otherwise, <c>false</c>.
            /// </value>
            public override bool FixtureSetUpTearDownInstance
            {
                get { return true; }
            }

            /// <summary>
            /// Gets the name of the fixture attribute
            /// </summary>
            /// <value>The fixture attribute.</value>
            public override TypeName FixtureAttribute
            {
                get { return Metadata<TestFixtureAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the fixture setup attribute
            /// </summary>
            /// <value>The fixture set up attribute.</value>
            public override TypeName FixtureSetUpAttribute
            {
                get { return Metadata<TestFixtureSetUpAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the fixture teardown attribute
            /// </summary>
            /// <value>The fixture tear down attribute.</value>
            public override TypeName FixtureTearDownAttribute
            {
                get { return Metadata<TestFixtureTearDownAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the test setup attribute.
            /// </summary>
            /// <value>The set up attribute.</value>
            public override TypeName SetUpAttribute
            {
                get { return Metadata<SetUpAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the test attribute.
            /// </summary>
            /// <value>The set up attribute.</value>
            public override TypeName TestAttribute
            {
                get { return Metadata<TestAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the test teardown attribute.
            /// </summary>
            /// <value>The tear down attribute.</value>
            public override TypeName TearDownAttribute
            {
                get { return Metadata<TearDownAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the ignore attribute.
            /// </summary>
            /// <value>The ignore attribute.</value>
            public override TypeName IgnoreAttribute
            {
                get { return Metadata<IgnoreAttribute>.SerializableName; }
            }

            /// <summary>
            /// Whether the ignore attribute constructor takes a message as its first argument.
            /// </summary>
            /// <value></value>
            protected override bool HasIgnoreAttributeMessage
            {
                get { return true; }
            }

            /// <summary>
            /// Gets the ignore message property.
            /// </summary>
            /// <value>The ignore message property.</value>
            protected override string IgnoreMessageProperty
            {
                get { return "Reason"; }
            }

            /// <summary>
            /// Gets the expected exception property name.
            /// </summary>
            /// <value>The expected exception property.</value>
            protected override string ExpectedExceptionProperty
            {
                get { return "ExceptionType"; }
            }

            /// <summary>
            /// Gets a list of attribute that should be duplicated from the
            /// pex test to the paramterized test
            /// </summary>
            /// <returns></returns>
            protected override IEnumerable<TypeName> GetSatelliteAttributeTypes()
            {
                return Indexable.Array<TypeName>(
                    this.CategoryAttribute,
                    Metadata<DescriptionAttribute>.SerializableName,
                    Metadata<ExplicitAttribute>.SerializableName,
                    Metadata<PlatformAttribute>.SerializableName,
                    Metadata<PropertyAttribute>.SerializableName
                    );
            }

            private TypeName CategoryAttribute
            {
                get { return Metadata<CategoryAttribute>.SerializableName; }
            }

            /// <summary>
            /// Tries the get categories.
            /// </summary>
            /// <param name="element">The element.</param>
            /// <param name="included">The included.</param>
            /// <param name="excluded">The excluded.</param>
            /// <returns></returns>
            protected override bool TryGetCategories(
                ICustomAttributeProviderEx element,
                out IEnumerable<string> names)
            {
                SafeDebug.AssumeNotNull(element, "element");

                SafeSet<string> categories = new SafeSet<string>();
                foreach (CategoryAttribute attribute in
                    AttributeHelper.GetAttributes(element, this.CategoryAttribute))
                {
                    categories.Add(attribute.Name);
                }

                names = categories.ToIndexable();
                return categories.Count > 0;
            }

            /// <summary>
            /// Gets the type of the assertion exception.
            /// </summary>
            /// <value>The type of the assertion exception.</value>
            public override TypeName AssertionExceptionType
            {
                get { return Metadata<AssertionException>.SerializableName; }
            }
        }
    }
}
